package lumis.portal.page.serialization;

import lumis.portal.PortalException;
import lumis.portal.PortalRequestParameters;
import lumis.portal.authentication.SessionConfig;
import lumis.portal.channel.ChannelConfig;
import lumis.portal.manager.ManagerException;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.manager.ManagerFactoryInternal;
import lumis.portal.metatag.IMetaTagManager;
import lumis.portal.metatag.MetaTagConfig;
import lumis.portal.page.PageConfig;
import lumis.portal.serialization.BaseSerializationContext;
import lumis.portal.serialization.Dependency;
import lumis.portal.serialization.DeserializationContext;
import lumis.portal.serialization.SerializationContext;
import lumis.portal.serialization.SerializationMessageConfigNode;
import lumis.portal.serialization.SerializationMessageConfigNode.RecursionStopPoints;
import lumis.portal.serialization.operation.ConfigDependencyAnalyzer;
import lumis.portal.serialization.operation.ConfigForeignKey;
import lumis.portal.serviceinterfaceinstance.IServiceInterfaceInstanceManager;
import lumis.portal.serviceinterfaceinstance.ServiceInterfaceInstanceConfig;
import lumis.portal.serviceinterfaceinstance.serialization.ServiceInterfaceInstanceSerializableConfig;
import lumis.util.ITransaction;

import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;

/**
 * Manager for Page serialization
 *
 * @version $Revision: 13220 $ $Date: 2011-07-08 13:52:53 -0300 (Fri, 08 Jul 2011) $
 * @since 4.0.7
 */
public class PageSerializationManager implements IPageSerializationManager
{
	private final String[] primaryKeyFieldNames = new String[] {"id"};
	protected final List<ConfigForeignKey> foreignKeys = Arrays.asList(new ConfigForeignKey[] {
			new ConfigForeignKey("channelId"),
			new ConfigForeignKey("accessControlListId"),
			new ConfigForeignKey("propertyBagId"),
			new ConfigForeignKey("parentTemplateId", null, true, false, true, ManagerFactory.getPageManager())
	});

	private final String configAlias = "page";
	
	// DESERIALIZATION
	
	private PageConfig deserializeConfig(DeserializationContext deserializationContext, String pageId) throws ManagerException
	{
		deserializationContext.logDebug("Deserializing page config");
		PageConfig pageConfig = ManagerFactoryInternal.getSerializationManager2()
				.deserializeObject(PageConfig.class, deserializationContext.getDirectory(),
						getConfigPath(deserializationContext, pageId), configAlias, false);

		try
		{
			pageConfig.isAutomaticFriendlyId();
		}
		catch (NullPointerException e)
		{
			// the automatic friendly id wasn't found
			pageConfig.setAutomaticFriendlyId(true);
		}

		// if friendly id wasn't deserialized, create an automatic one (for
		// compatibility reason)
		if (pageConfig.getFriendlyId() == null && pageConfig.isAutomaticFriendlyId())
		{
			pageConfig.setAutoGeneratedFriendlyId();
		}

		return pageConfig;
	}

	public void deserialize(DeserializationContext deserializationContext, PageSerializableConfig pageSerializableConfig) throws PortalException
	{
		deserializationContext.logDebug("Deserializing page '" + pageSerializableConfig.getId() + "'");

		// notify progress
		deserializationContext.updateProgressBar(0, 1, "STR_IMPORTING_PAGE;" + deserializationContext.getCurrentChannelPath() + ";" + pageSerializableConfig.getName());

		// deserialize access control list
		ManagerFactoryInternal.getPageAclSerializationManager().deserialize(deserializationContext, pageSerializableConfig.getAccessControlList());
		
		// deserialize config
		PageConfig pageConfig = deserializeConfig(deserializationContext, pageSerializableConfig.getId());
		
		// apply primary key id mappings
		deserializationContext.applyPrimaryKeyFieldIdMappings(pageConfig, primaryKeyFieldNames);
		
		// deserialize property bag
		String channelId = deserializationContext.getIdMapping(pageConfig.getChannelId());
		ChannelConfig channelConfig = ManagerFactory.getChannelManager().get(
				deserializationContext.getSessionConfig(), channelId, 
				deserializationContext.getTransaction());
		ManagerFactoryInternal.getPropertyBagSerializationManager().deserializePropertyBag(
				deserializationContext, pageConfig.getPropertyBagId(), channelConfig.getPropertyBagId());
		
		// analyze dependencies and persist config
		PageDependencyAnalyzer dependencyAnalyzer = new PageDependencyAnalyzer(deserializationContext, foreignKeys, pageConfig);
		dependencyAnalyzer.persist(new PageConfigPersister(foreignKeys, pageConfig.getLayout()), true);
		
		// persist dependent configs
		deserializationContext.persistDependentConfigs(pageSerializableConfig.getId());
		
		// deserialize metaTags
		deserializeMetaTags(deserializationContext, pageSerializableConfig);
		
		// deserialize page css
		deserializeCsss(deserializationContext, pageSerializableConfig, pageConfig);

		// remove current service interface instances
		deserializationContext.logDebug(" Deleting current service interface instances");

		// deserialize service interface instances
		for (ServiceInterfaceInstanceSerializableConfig serviceInterfaceInstanceSerializableConfig : pageSerializableConfig.getServiceInterfaceInstances())
			ManagerFactoryInternal.getServiceInterfaceInstanceSerializationManager().deserialize(deserializationContext, serviceInterfaceInstanceSerializableConfig);
	}

	private void deserializeMetaTags(DeserializationContext deserializationContext,
			PageSerializableConfig pageSerializableConfig) throws PortalException
	{
		ITransaction transaction = deserializationContext.getTransaction();
		SessionConfig sessionConfig = deserializationContext.getSessionConfig();
		IMetaTagManager metaTagManager = ManagerFactoryInternal.getMetaTagManager();
		
		String pageId = deserializationContext.getIdMapping(pageSerializableConfig.getId());
		
		// delete existing metaTags for the page
		List<String> metaTagIds = metaTagManager.getIdsByParentId(sessionConfig, 
				pageId, transaction);
		for (String metaTagId: metaTagIds)
			metaTagManager.delete(sessionConfig, metaTagId, transaction);
		
		// add deserialized metaTags to the channel
		for (MetaTagConfig metaTagConfig: pageSerializableConfig.getMetaTags())
		{
			deserializationContext.applyPrimaryKeyFieldIdMappings(metaTagConfig, new String[] {"id"});
			deserializationContext.applyForeignKeyFieldIdMappings(metaTagConfig, new String[] {"parentId"});
			metaTagManager.add(sessionConfig, metaTagConfig, transaction);
		}
	}

	private void deserializeCsss(DeserializationContext deserializationContext, PageSerializableConfig pageSerializableConfig, PageConfig pageConfig) throws PortalException
	{
		Collection<String> oldCssIds = null;
		if (!deserializationContext.isImportAsChild())
			oldCssIds = ManagerFactory.getPageManager().getCssIds(deserializationContext.getSessionConfig(), pageSerializableConfig.getId(), deserializationContext.getTransaction());
		for (int i = 0; i < pageSerializableConfig.getCsss().size(); i++)
		{
			String cssId = pageSerializableConfig.getCsss().get(i);
			if (cssId != null)
			{
				boolean addCssLink = false;
				if (!deserializationContext.isImportAsChild()) // add the CSS link only if it does not already exist
					addCssLink = !oldCssIds.remove(cssId);
				else
					addCssLink = true;
				
				if (addCssLink)
					ManagerFactory.getPageManager().addCssLink(deserializationContext.getSessionConfig(), pageConfig.getId(), cssId, i, deserializationContext.getTransaction());
			}
		}

		if (!deserializationContext.isImportAsChild())
			for (String oldCssId : oldCssIds)
				ManagerFactory.getPageManager().deleteCssLink(deserializationContext.getSessionConfig(), pageConfig.getId(), oldCssId, deserializationContext.getTransaction());
	}
	
	public boolean persistConfig(DeserializationContext deserializationContext, Object config, boolean firstPersistence) throws PortalException
	{
		boolean persisted = false;

		PageConfig pageConfig = (PageConfig)config;

		// apply foreign key id mappings and analyze dependencies
		ConfigDependencyAnalyzer<PageConfig> dependencyAnalyzer = new ConfigDependencyAnalyzer<PageConfig>(deserializationContext, foreignKeys, pageConfig);
		if (dependencyAnalyzer.foreignKeysExists())
		{
			// add or update config
			if (deserializationContext.isUpdateIfElementExists() || !firstPersistence)
			{
				deserializationContext.logDebug("Adding/updating page config:", pageConfig);
				ManagerFactory.getPageManager().addOrUpdate(deserializationContext.getSessionConfig(), dependencyAnalyzer.getMappedPersistableConfig(), deserializationContext.getTransaction());
			}
			else
			{
				deserializationContext.logDebug("Adding page config:", pageConfig);
				ManagerFactory.getPageManager().add(deserializationContext.getSessionConfig(), dependencyAnalyzer.getMappedPersistableConfig(), deserializationContext.getTransaction());
			}
			persisted = true;
		}		
		return persisted;
	}

	// SERIALIZATION
	
	public PageSerializableConfig serialize(SerializationContext serializationContext, String pageId, boolean deepSerialization) throws PortalException
	{
		// get config
		PageConfig pageConfig = ManagerFactory.getPageManager().get(serializationContext.getSessionConfig(), pageId, serializationContext.getTransaction());

		// build structure object
		PageSerializableConfig pageSerializableConfig = createSerializableConfig(pageConfig);
		
		if (deepSerialization)
		{
			setupSerializationContextForDeepDeserialization(serializationContext);

			// notify progress
			serializationContext.updateProgressBar(0, 1, "STR_EXPORTING_PAGE;" + serializationContext.getCurrentChannelPath() + ";" + pageConfig.getName());

			// serialize config
			ManagerFactoryInternal.getSerializationManager2().serializeObject(pageConfig, serializationContext.getDirectory(), getConfigPath(serializationContext, pageId), configAlias);
			
			// analyze external dependencies
			analyzeExternalDependencies(serializationContext, pageConfig);

			// serialize access control list
			pageSerializableConfig.setAccessControlList(ManagerFactoryInternal.getPageAclSerializationManager().serialize(serializationContext, pageId));
			
			// serialize property bag
			ManagerFactoryInternal.getPropertyBagSerializationManager().serialize(serializationContext, pageConfig.getPropertyBagId());

			// serialize metaTags
			List<String> pageMetaTagIds = ManagerFactoryInternal.getMetaTagManager().getIdsByParentId(
					serializationContext.getSessionConfig(), pageId, serializationContext.getTransaction());
			for (String metaTagId: pageMetaTagIds)
			{
				MetaTagConfig metaTagConfig = ManagerFactoryInternal.getMetaTagManager().get(
						serializationContext.getSessionConfig(), metaTagId, serializationContext.getTransaction());
				pageSerializableConfig.getMetaTags().add(metaTagConfig);
			}
			
			// serialize page css
			for (String cssId : ManagerFactory.getPageManager().getCssIds(serializationContext.getSessionConfig(), pageId, serializationContext.getTransaction()))
			{
				pageSerializableConfig.getCsss().add(cssId);
				serializationContext.getReferencedCssIds().add(cssId);
			}
			
			// serialize service interface instances
			IServiceInterfaceInstanceManager serviceInterfaceInstanceManager = ManagerFactory.getServiceInterfaceInstanceManager();
			for (String serviceInterfaceInstanceId : serviceInterfaceInstanceManager.getIdsByPageId(serializationContext.getSessionConfig(), pageId, serializationContext.getTransaction()))
			{
				ServiceInterfaceInstanceConfig interfaceInstanceConfig = serviceInterfaceInstanceManager.get(serializationContext.getSessionConfig(), serviceInterfaceInstanceId, serializationContext.getTransaction());
				if (!interfaceInstanceConfig.isCreatedRunTime())
					pageSerializableConfig.getServiceInterfaceInstances().add(ManagerFactoryInternal.getServiceInterfaceInstanceSerializationManager().serialize(serializationContext, serviceInterfaceInstanceId));
			}
		}
		else
		{
			// in shallow serialization, only id and name should be serialized (other fields are marked as transient)
			setupSerializationContextForShallowDeserialization(serializationContext);
 		}

		return pageSerializableConfig;
	}

	private void analyzeExternalDependencies(SerializationContext serializationContext, PageConfig dependentPageConfig) throws PortalException
	{
		if (dependentPageConfig.getParentTemplateId() != null)
		{
			PageConfig dependablePageTemplateConfig = ManagerFactory.getPageManager().get(serializationContext.getSessionConfig(), dependentPageConfig.getParentTemplateId(), serializationContext.getTransaction());

			// if the page template is outside the exporting channel tree, treat it 
			// as an external dependency
			if (!serializationContext.getChannelIds().contains(dependablePageTemplateConfig.getChannelId()))
			{
				Dependency dependency = new Dependency();
				dependency.setDependentNode(getSerializationMessageConfigNode(serializationContext, dependentPageConfig));
				dependency.setDependableNode(getSerializationMessageConfigNode(serializationContext, dependablePageTemplateConfig));

				serializationContext.getExternalDependencies().add(dependency);
			}
		}		

	}

	private PageSerializableConfig createSerializableConfig(PageConfig pageConfig)
	{
		PageSerializableConfig pageSerializableConfig = new PageSerializableConfig();
		pageSerializableConfig.setId(pageConfig.getId());
		pageSerializableConfig.setName(pageConfig.getName());
		return pageSerializableConfig;
	}

	// GENERAL
	
	public void setupSerializationContext(BaseSerializationContext baseSerializationContext)
	{
		// define serialization aliases
		baseSerializationContext.getSerializationSettings().getClassAliases().put(PageSerializableConfig.class, "page");

		// in shallow serialization, only id and name should be serialized (other fields are marked as transient)
		setupSerializationContextForShallowDeserialization(baseSerializationContext);
	}

	private void setupSerializationContextForShallowDeserialization(BaseSerializationContext baseSerializationContext)
	{
		// in shallow serialization, only id and name should be serialized (other fields are marked as transient)
		Set<String> transientFieldsForClass = baseSerializationContext.getSerializationSettings().getTransientFieldsForClass(PageSerializableConfig.class);
		transientFieldsForClass.add("serviceInterfaceInstances");
		transientFieldsForClass.add("metaTags");
		transientFieldsForClass.add("csss");
		transientFieldsForClass.add("accessControlList");
	}

	private void setupSerializationContextForDeepDeserialization(BaseSerializationContext baseSerializationContext)
	{
		// mark for serialize collection fields (no transient)
		baseSerializationContext.getSerializationSettings().getTransientFieldsForClass(PageSerializableConfig.class).clear();
		baseSerializationContext.getSerializationSettings().getClassAliases().put(MetaTagConfig.class, "metaTag");
	}

	/**
	 * Get the full path for the serialization file.
	 * @param baseSerializationContext
	 * @param configId
	 * @return
	 */
	private String getConfigPath(BaseSerializationContext baseSerializationContext, String configId)
	{
		return configId + ".xml";
	}

	public SerializationMessageConfigNode getSerializationMessageConfigNode(BaseSerializationContext baseSerializationContext, PageConfig pageConfig) throws PortalException
	{
		return getSerializationMessageConfigNode(baseSerializationContext, pageConfig, EnumSet.of(RecursionStopPoints.None));
	}

	public SerializationMessageConfigNode getSerializationMessageConfigNode(BaseSerializationContext baseSerializationContext, PageConfig pageConfig, EnumSet<RecursionStopPoints> recursionStopPoints) throws PortalException
	{
		ChannelConfig channelConfig = ManagerFactory.getChannelManager().get(baseSerializationContext.getSessionConfig(), pageConfig.getChannelId(), baseSerializationContext.getTransaction());

		PageSerializationMessageConfig pageSerializationMessageConfig = (pageConfig.isTemplate() ? new PageTemplateSerializationMessageConfig() : new PageSerializationMessageConfig());;
		pageSerializationMessageConfig.setId(pageConfig.getId());
		pageSerializationMessageConfig.setName(pageConfig.getName());

		SerializationMessageConfigNode serializationMessageConfigNode = new SerializationMessageConfigNode();
		serializationMessageConfigNode.setSerializationMessageConfig(pageSerializationMessageConfig);
		if (!recursionStopPoints.contains(RecursionStopPoints.Channel))
			serializationMessageConfigNode.getParentNodes().add(ManagerFactoryInternal.getChannelSerializationManager().getSerializationMessageConfigNode(baseSerializationContext, channelConfig));
		serializationMessageConfigNode.setNavigateScript("window.parent.opener.document.location.href='../../../../"+PageConfig.PAGE_MAIN+"?"+PortalRequestParameters.PAGE_PARAMETER_PAGEID+"="+pageConfig.getId()+"';return false;");
		
		return serializationMessageConfigNode;
	}
}
